﻿using DBUtil.MetaData;
using DotNetCommon;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DBUtil.Generators
{
    /// <summary>
    /// 基于应用程序缓存的Id和流水号生成控制器
    /// </summary>
    public class IdAndSNOGeneratorProcessCache
    {
        /// <summary>
        /// 默认数据库
        /// </summary>
        public const string DefaultDBKey = "默认";
        private const string splitstr = "__^^-^^__";
        private DBAccess _db = null;

        internal IdAndSNOGeneratorProcessCache(DBAccess db)
        {
            this._db = db;
        }

        #region Id生成控制
        private static ConcurrentDictionary<string, object> ht_ids = new ConcurrentDictionary<string, object>();
        private static ConcurrentDictionary<string, object> ht_locks = new ConcurrentDictionary<string, object>();
        private static ConcurrentDictionary<string, object> ht_locks2 = new ConcurrentDictionary<string, object>();
        private object GeneLock(string dbkey, string tableName, string colName)
        {
            var key = GeneKey(dbkey, tableName, colName);
            return ht_locks.GetOrAdd(key, str => new object());
        }

        private object GeneLock2(string dbkey, string tableName, string colName)
        {
            var key = GeneKey(dbkey, tableName, colName);
            return ht_locks2.GetOrAdd(key, str => new object());
        }

        private string GeneKey(string dbkey, string tableName, string colName)
        {
            return dbkey + splitstr + tableName + splitstr + colName;
        }

        /// <summary>
        /// 根据表名和列名生成Id,第一次生成后就不需要再访问数据库,频率高时使用
        /// </summary>
        /// <param name="dbkey">数据库标识(基于此实现针对不同数据库的生成控制)</param>
        /// <param name="tableName">表名</param>
        /// <param name="colName">列名</param>
        /// <returns></returns>
        public long NewId(string dbkey, string tableName, string colName)
        {
            if (string.IsNullOrWhiteSpace(dbkey)) dbkey = DefaultDBKey;
            var lockObj = GeneLock(dbkey, tableName, colName);
            var key2 = GeneKey(dbkey, tableName, colName);
            lock (lockObj)
            {
                object obj;
                if (!ht_ids.TryGetValue(key2, out obj))
                {
                    string str = _db.SelectScalar<string>(string.Format("select max({0}) from {1}", colName, tableName));
                    if (string.IsNullOrWhiteSpace(str))
                    {
                        obj = 1;
                    }
                    else
                    {
                        obj = int.Parse(str) + 1;
                    }
                    ht_ids.TryAdd(key2, obj);
                }
                else
                {
                    obj = int.Parse(obj.ToString()) + 1;
                    ht_ids[key2] = obj;
                }
                return int.Parse(obj.ToString());
            }
        }

        /// <summary>
        /// 重置一个表的Id,这将删除对应[数据库-表-列]在进程内的生成缓存
        /// </summary>
        /// <param name="dbkey">数据库标识(基于此实现针对不同数据库的生成控制)</param>
        /// <param name="tableName">表名</param>
        /// <param name="colName">列名</param>
        /// <returns></returns>
        public void ResetId(string dbkey, string tableName, string colName)
        {
            if (string.IsNullOrWhiteSpace(dbkey)) dbkey = DefaultDBKey;
            string key2 = GeneKey(dbkey, tableName, colName);
            lock (key2)
            {
                if (ht_ids.ContainsKey(key2))
                {
                    object obj;
                    ht_ids.TryRemove(key2, out obj);
                }
            }
        }

        /// <summary>
        /// 显示当前环境下的当前ID
        /// </summary>
        /// <param name="dbkey">数据库标识(基于此实现针对不同数据库的生成控制)</param>
        /// <param name="tableName">如果指定了tableName就只显示这个表的ID控制情况</param>
        /// <param name="colName">如果指定了colName就显示这个表的这个字段的ID控制情况</param>
        /// <returns></returns>
        public List<Tuple<string, string, string, long>> ShowCurrentIds(string dbkey = null, string tableName = null, string colName = null)
        {
            List<Tuple<string, string, string, long>> res = new List<Tuple<string, string, string, long>>();
            var prefix = "";
            if (!string.IsNullOrWhiteSpace(dbkey)) prefix += dbkey + splitstr;
            var keys = ht_ids.Keys.ToList().Where(_ => _.StartsWith(prefix)).ToList();
            foreach (var key2 in keys)
            {
                string id = ht_ids[key2].ToString();
                string[] arr = key2.ToString().Split(new string[] { splitstr }, StringSplitOptions.RemoveEmptyEntries);
                var dbkey2 = arr[0];
                string table = arr[1];
                string col = arr[2];
                res.Add(new Tuple<string, string, string, long>(dbkey, table, col, long.Parse(id)));
            }
            if (!string.IsNullOrWhiteSpace(tableName))
            {
                for (int i = res.Count - 1; i >= 0; i--)
                {
                    if (!res[i].Item1.StartsWith(tableName))
                    {
                        res.RemoveAt(i);
                    }
                }
            }
            if (!string.IsNullOrWhiteSpace(colName))
            {
                for (int i = res.Count - 1; i >= 0; i--)
                {
                    if (!res[i].Item2.StartsWith(colName))
                    {
                        res.RemoveAt(i);
                    }
                }
            }
            return res;
        }
        #endregion

        #region 流水号生成控制
        private static ConcurrentDictionary<string, ConcurrentDictionary<string, long>> snoCaches = new ConcurrentDictionary<string, ConcurrentDictionary<string, long>>();


        /// <summary>
        /// 根据表名和列名生成流水号
        /// </summary>
        /// <param name="dbkey">数据库标识(基于此实现针对不同数据库的生成控制)</param>
        /// <param name="tableName">表名</param>
        /// <param name="colName">列名</param>
        /// <param name="format">流水号格式</param>
        /// <returns></returns>
        public string NewSNO(string dbkey, string tableName, string colName, SerialFormat format)
        {
            if (string.IsNullOrWhiteSpace(dbkey)) dbkey = DefaultDBKey;
            dbkey = GeneKey(dbkey, tableName, colName);
            var lockObj = GeneLock2(dbkey, tableName, colName);
            lock (lockObj)
            {
                var caches = snoCaches.GetOrAdd(dbkey, new ConcurrentDictionary<string, long>());
                DateTime now = DateTime.Now;
                var nowstr = GetDateNo(format, now);
                if (!caches.TryGetValue(nowstr, out long id))
                {
                    //查找数据库
                    int startindex = 0;
                    var parseResult = SerialFormat.Parse(format, now, DotNetCommon.DistributeGenerator.MachineId);
                    var likestr = parseResult.Item1;
                    startindex = parseResult.Item3;
                    var lastsno = _db.SelectScalar<string>($"select {colName} from {tableName} where {colName} like '{likestr}' order by {colName} desc");
                    if (string.IsNullOrWhiteSpace(lastsno))
                    {
                        id = 0;
                    }
                    else
                    {
                        id = long.Parse(lastsno.Substring(startindex));
                    }
                }
                id++;
                caches[nowstr] = id;
                var chunk = format.Chunks.FirstOrDefault(i => i.Type == SerialFormatChunkType.DateText);
                //组装流水号
                var chunks = format.Chunks;
                var sno = "";
                for (int i = 0, len = chunks.Count; i < len; i++)
                {
                    chunk = chunks[i];
                    if (chunk.Type == SerialFormatChunkType.StaticText)
                    {
                        sno += chunk.FormatString;
                    }
                    else if (chunk.Type == SerialFormatChunkType.DateText)
                    {
                        sno += nowstr;
                    }
                    else if (chunk.Type == SerialFormatChunkType.MachineText)
                    {
                        sno += DotNetCommon.DistributeGenerator.MachineId.ToString().PadLeft(4, '0');
                    }
                    else if (chunk.Type == SerialFormatChunkType.SerialNo)
                    {
                        var s = id.ToString();
                        if (s.Length > chunk.Length)
                        {
                            sno += s;
                        }
                        else
                        {
                            sno += s.PadLeft(chunk.Length, '0');
                        }
                    }
                }
                return sno;
            }
        }

        private static string GetDateNo(SerialFormat format, DateTime now)
        {
            var chunk = format.Chunks.FirstOrDefault(i => i.Type == SerialFormatChunkType.DateText);
            return now.ToString(chunk == null ? "yyyyMMdd" : chunk.FormatString);
        }

        /// <summary>
        /// 重置流水号(直接删除程序中的缓存)
        /// </summary>
        /// <param name="dbkey">数据库标识(基于此实现针对不同数据库的生成控制)</param>
        /// <param name="tableName">表名</param>
        /// <param name="colName">列名</param>
        public void ResetSNO(string dbkey, string tableName, string colName)
        {
            if (!string.IsNullOrWhiteSpace(dbkey)) dbkey = DefaultDBKey;
            var prefix = GeneKey(dbkey, tableName, colName);
            snoCaches.TryRemove(prefix, out ConcurrentDictionary<string, long> cache);
        }

        /// <summary>
        /// 显示当前的流水号生成进度
        /// </summary>
        /// <param name="dbkey">数据库标识(基于此实现针对不同数据库的生成控制)</param>
        /// <param name="tableName">表名,为空表示所有的表</param>
        /// <param name="colName">列名,为空表示所有的列</param>
        /// <returns></returns>
        public List<Tuple<string, string, string, string, long>> ShowCurrentSNOs(string dbkey = null, string tableName = null, string colName = null)
        {
            var res = new List<Tuple<string, string, string, string, long>>();
            var prefix = "";
            if (!string.IsNullOrWhiteSpace(dbkey))
            {
                prefix += dbkey;
                if (!string.IsNullOrWhiteSpace(tableName))
                {
                    prefix += splitstr + tableName;
                    if (!string.IsNullOrWhiteSpace(colName))
                    {
                        prefix += splitstr + colName;
                    }
                }
            }
            var keys = snoCaches.Keys.ToList().Where(_ => _.StartsWith(prefix)).ToList();
            foreach (var key2 in keys)
            {
                var cache = snoCaches[key2];
                string[] arr = key2.ToString().Split(new string[] { splitstr }, StringSplitOptions.RemoveEmptyEntries);
                string dbkey2 = arr[0];
                string table = arr[1];
                string col = arr[2];
                var datestr = cache.Keys.ToList().OrderBy(_ => _).FirstOrDefault();
                var id = 0L;
                if (datestr != null) id = long.Parse(cache[datestr].ToString());
                res.Add(new Tuple<string, string, string, string, long>(dbkey2, table, col, datestr, id));
            }
            return res;
        }
        #endregion
    }
}
